function HTMLActuator() {
    this.tileContainer    = document.querySelector(".tile-container")
    this.scoreContainer   = document.querySelector(".score-container")
    this.bestContainer    = document.querySelector(".best-container")
    this.messageContainer = document.querySelector(".game-message")
    this.score = 0
}
  
HTMLActuator.prototype.actuate = function (grid, metadata) {
    var self = this
    window.requestAnimationFrame(function () {
        self.clearContainer(self.tileContainer)
        grid.cells.forEach(function (column) {
            column.forEach(function (cell) {
                if (cell) {
                    self.addTile(cell)
                }
            })
        })
  
        self.updateScore(metadata.score)
        self.updateBestScore(metadata.bestScore)
  
        if (metadata.terminated) {
            if (metadata.over) {
            self.message(false)
            } else if (metadata.won) {
            self.message(true)
            }
        }
    })
}
  
HTMLActuator.prototype.continueGame = function () {
    this.clearMessage()
}
  
HTMLActuator.prototype.clearContainer = function (container) {
    while (container.firstChild) {
        container.removeChild(container.firstChild)
    }
}
  
HTMLActuator.prototype.addTile = function (tile) {
    var self = this
    var wrapper   = document.createElement("div")
    var inner     = document.createElement("div")
    var position  = tile.previousPosition || { x: tile.x, y: tile.y }
    var positionClass = this.positionClass(position)
    var classes = ["tile", "tile-" + tile.value, positionClass]
  
    if (tile.value > 2048) classes.push("tile-super")

    this.applyClasses(wrapper, classes)
  
    inner.classList.add("tile-inner")
    inner.textContent = tile.value
  
    if (tile.previousPosition) {
        window.requestAnimationFrame(function () {
            classes[2] = self.positionClass({ x: tile.x, y: tile.y })
            self.applyClasses(wrapper, classes)
        })
    } else if (tile.mergedFrom) {
        classes.push("tile-merged")
        this.applyClasses(wrapper, classes)
        tile.mergedFrom.forEach(function (merged) {
            self.addTile(merged)
        })
    } else {
        classes.push("tile-new")
        this.applyClasses(wrapper, classes)
    }

    wrapper.appendChild(inner)
    this.tileContainer.appendChild(wrapper)
}
  
HTMLActuator.prototype.applyClasses = function (element, classes) {
    element.setAttribute("class", classes.join(" "))
}

HTMLActuator.prototype.normalizePosition = function (position) {
    return { x: position.x + 1, y: position.y + 1 }
}

HTMLActuator.prototype.positionClass = function (position) {
    position = this.normalizePosition(position)
    return "tile-position-" + position.x + "-" + position.y
}
  
HTMLActuator.prototype.updateScore = function (score) {
    this.clearContainer(this.scoreContainer)
    var difference = score - this.score
    this.score = score
    this.scoreContainer.textContent = this.score
  
    if (difference > 0) {
        var addition = document.createElement("div")
        addition.classList.add("score-addition")
        addition.textContent = "+" + difference
        this.scoreContainer.appendChild(addition)
    }
}
  
HTMLActuator.prototype.updateBestScore = function (bestScore) {
    this.bestContainer.textContent = bestScore
}

HTMLActuator.prototype.message = function (won) {
    var type    = won ? "game-won" : "game-over"
    var message = won ? "You win!" : "Game over!"

    this.messageContainer.classList.add(type)
    this.messageContainer.getElementsByTagName("p")[0].textContent = message
}
  
HTMLActuator.prototype.clearMessage = function () {
    this.messageContainer.classList.remove("game-won")
    this.messageContainer.classList.remove("game-over")
}